package com.mk.util.pay;

import act.app.ActionContext;
import act.app.App;
import act.event.ActEvent;
import act.event.EventBus;
import com.alibaba.fastjson.JSON;
import com.mk.Starter;
import com.mk.util.Redis;
import com.mk.enums.ErrorCode;
import com.mk.enums.GlobalValues;
import com.mk.exception.UserException;
import com.mk.properties.AppConfig;
import com.mk.util.EncyUtils;
import com.mk.util.HttpUtils;
import com.mk.util.MapUtil;
import com.mk.util.S2;
import org.apache.commons.collections.MapUtils;
import org.osgl.util.S;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

import static com.mk.util.HttpUtils.doPostSSL;


@SuppressWarnings("all")
public class WeixinPayUtils extends PayUtils {
    private final String orderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
    private final String orderRefundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";
    private final String orderPayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    private final String closeOrderUrl = "https://api.mch.weixin.qq.com/pay/closeorder";
    //企业转账地址
    private final String transfersUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
    /**
     * 微信签名类型
     */
    private final String sign_type = "MD5";
    private String appid = AppConfig.weixin.appid.get();
    private String mch_id = AppConfig.weixin.mch_id.get();
    private String api_key = AppConfig.weixin.api_key.get();
    private String sslpath = AppConfig.weixin.sslpath.get();
    private String info_des = "美多拼";

    /**
     * 发送请求
     *
     * @param url
     * @param map
     * @return
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    private Map<String, String> doPostUrl(String url, Map<String, String> map) throws ParserConfigurationException, IOException, SAXException {
        //获取本地签名
        String xmlStr = getSign(map, "xml");
        //向微信发起支付申请
        String result = HttpUtils.doPost(url, xmlStr);
        Starter.LOG.error("执行付款操作结果:" + JSON.toJSONString(result));
        //region 发事件,保存请求信息
        saveParamResult(url, map, result);
        //endregion 发事件,保存请求信息
        Map<String, String> payMap = PayUtils.getMapFromXML(result);
        if (!S.eq("SUCCESS", payMap.get("return_code"))) {
            throw new UserException(payMap.get("return_msg"));
        }
        if (!S.eq("SUCCESS", payMap.get("result_code"))) {
            throw new UserException(payMap.get("err_code_des"));
        }
        //将支付金额放入支付对象结果中
//        BigDecimal total_fee = MapUtil.getDg(map,"total_fee",BigDecimal.ZERO);
//        total_fee = total_fee.divide(new BigDecimal("100"));
//        payMap.put("total_fee",total_fee.toString());
        payMap.put("package", "prepay_id=" + payMap.get("prepay_id"));
        return payMap;
    }

    /**
     * 带证书请求
     *
     * @param url
     * @param map
     * @return
     * @throws Exception
     */
    private Map<String, String> doPostUrlSSL(String url, Map<String, String> map) throws Exception {
        //获取本地签名
        String xmlStr = getSign(map, "xml");
        //向微信发起支付申请
        String result = doPostSSL(url, xmlStr, sslpath, mch_id);
        saveParamResult(url, map, result);
        Map<String, String> payMap = PayUtils.getMapFromXML(result);
        if (!S.eq("SUCCESS", payMap.get("return_code"))) {
            throw new UserException(payMap.get("return_msg"));
        }
        if (!S.eq("SUCCESS", payMap.get("result_code"))) {
            throw new UserException(payMap.get("err_code_des"));
        }
        return payMap;
    }

    /**
     * 通过发事件,保存信息
     *
     * @param url
     * @param map
     * @param result
     */
    private void saveParamResult(String url, Map<String, String> map, String result) {
        //region 发事件,保存请求信息
        EventBus eventBus = App.instance().getInstance(EventBus.class);
        Map<String, Object> eventMap = new HashMap<>();
        eventMap.put("url", url);
        eventMap.put("params", map);
        eventMap.put("result", result);
        eventMap.put("paytype", "WX");
        eventBus.trigger("PAY_PARAM_RESULT", new ActEvent<>(eventMap));
        //endregion 发事件,保存请求信息
    }

    /**
     * 通过微信支付对象获取
     * total_fee传入是以元为单位
     *
     * @param paramMap         支付参数（body,out_trade_no,total_fee,ipAddress,userid,username）当trade_type=='NATIVE'时,必传 product_id
     * @param trade_type:JSAPI 公众号支付 ,NATIVE 扫码支付, APP APP支付
     * @return
     */
    private Map<String, String> getPayObjectComm(String trade_type, Map<String, String> paramMap) {
        HashMap<String, String> map = new HashMap<>();
        String body = info_des + "-" + paramMap.get("body");
        body = body.replaceAll("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]", "");
        if (S.isNotEmpty(body) && body.length() > 40) {
            body = body.substring(0, 40);
        }
        map.put("body", body);
        map.put("out_trade_no", paramMap.get("out_trade_no"));
        map.put("attach", paramMap.get("attach"));//自定义参数,支付回调原值返回.通常用来传入业务参数
        map.put("total_fee", new BigDecimal(paramMap.get("total_fee")).multiply(new BigDecimal(100)).intValue() + "");
        map.put("trade_type", trade_type);
        map.put("spbill_create_ip", paramMap.get("ipAddress"));

        map.put("nonce_str", S2.randomNumberString(6));
        map.put("sign_type", sign_type);
        map.put("appid", appid);
        map.put("mch_id", mch_id);
        map.put("notify_url", AppConfig.weixin.callback.get());
        //公众号支付时必传参数openid
        if (S.eq(trade_type, "JSAPI")) {
            if (S.isBlank(paramMap.get("openid"))) {
                throw new UserException("必须传入微信openid参数");
            }
            map.put("openid", paramMap.get("openid"));
        }
        //扫码支付时必传参数
        if (S.eq(trade_type, "NATIVE")) {
            if (S.isBlank(paramMap.get("product_id"))) {
                throw new UserException("扫码支付必须传product_id参数");
            }
            map.put("product_id", paramMap.get("product_id"));
        }
        Map<String, String> payMap;
        try {
            payMap = doPostUrl(orderPayUrl, map);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new UserException(ErrorCode.E401001);
        }
        return payMap;
    }

    private Map<String, String> payObjResult(Map<String, String> payMap) {
        //申请结束
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("appId", payMap.get("appid"));
        resultMap.put("timeStamp", (new Date()).getTime() / 1000 + "");
        resultMap.put("nonceStr", payMap.get("nonce_str"));
        resultMap.put("signType", sign_type);
        resultMap.put("package", payMap.get("package"));
        resultMap.put("paySign", getSign(resultMap, "sign"));

//        resultMap.put("total_fee", payMap.get("total_fee"));

//        resultMap.put("code_url", payMap.get("code_url"));
        return resultMap;
    }

    /**
     * 获取支付对象 APP
     *
     * @param paramMap 支付参数（body,out_trade_no,total_fee,ipAddress,userid,username）
     * @return
     */
    @Override
    public Map<String, String> getPayObject(Map<String, String> paramMap) {
        return payObjResult(getPayObjectComm("APP", paramMap));
    }

    /**
     * 获取支付对象 公众号
     *
     * @param paramMap 支付参数（body,out_trade_no,total_fee,ipAddress,userid,username）
     * @return
     */
    @Override
    public Map<String, String> getPayObjectGzh(Map<String, String> paramMap) {
        return payObjResult(getPayObjectComm("JSAPI", paramMap));
    }

    /**
     * 获取支付对象 H5
     *
     * @param paramMap 支付参数（body,out_trade_no,total_fee,ipAddress,userid,username）
     * @return
     */
    @Override
    public Map<String, String> getPayObjecth5(Map<String, String> paramMap) {
        return payObjResult(getPayObjectComm("MWEB", paramMap));
    }

    /**
     * 获取支付对象 二维码链接
     *
     * @param paramMap 支付参数（body,out_trade_no,total_fee,ipAddress,userid,username）product_id
     * @return
     */
    @Override
    public String getPayCodeUrl(Map<String, String> paramMap) {
        Map<String, String> payMap = getPayObjectComm("NATIVE", paramMap);
        return payMap.get("code_url");
    }

    /*
     * 查询订单支付情况
     * */
    @Override
    public Map<String, String> getOrderInfo(String payid) {
        Map<String, String> queryMap = new HashMap<String, String>();
        queryMap.put("appid", appid);
        queryMap.put("mch_id", mch_id);
        queryMap.put("out_trade_no", payid);
        queryMap.put("nonce_str", S2.randomNumberString(6));
        String xmlStr = getSign(queryMap, "xml");
        String result = HttpUtils.doPost(this.orderQueryUrl, xmlStr);
        try {
            return PayUtils.getMapFromXML(result);
        } catch (Exception ex) {
            throw new UserException("网络错误，请稍后重试！");
        }
    }

    @Override
    public boolean closeOrder(String payid) {
        Map<String, String> queryMap = new HashMap<>();
        queryMap.put("appid", appid);
        queryMap.put("mch_id", mch_id);
        queryMap.put("out_trade_no", payid);
        queryMap.put("nonce_str", S2.randomNumberString(6));
        String xmlStr = getSign(queryMap, "xml");
        String result = HttpUtils.doPost(this.closeOrderUrl, xmlStr);
        Map<String, String> resultMap;
        try {
            resultMap = PayUtils.getMapFromXML(result);
        } catch (Exception ex) {
            throw new UserException("订单错误，请刷新后重试！");
        }
        if (S.eq(MapUtils.getString(resultMap, "result_code"), GlobalValues.WEIXINRESULT_SUCCESS.value())) {
            return true;
        }
        if (S.eq(MapUtils.getString(resultMap, "err_code"), "ORDERPAID")) {
            throw new UserException(ErrorCode.E501001);
        }
        if (S.eq(MapUtils.getString(resultMap, "err_code"), "SYSTEMERROR")) {
            throw new UserException("微信支付异常，请稍后重试");
        }
        return true;
    }

    /**
     * 验证订单支付状态
     */
    @Override
    public Map<String, String> validatePayidState(String payid) throws UserException {
        Map<String, String> map = getOrderInfo(payid);
        String return_code = map.get("return_code");
        String result_code = map.get("result_code");
        String trade_state = map.get("trade_state");
        Map<String, String> jsonObj;
        if ("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code) && "SUCCESS".equals(trade_state)) {
            ;//||trade_state.contains("REFUND");
            jsonObj = JSON.parseObject(map.get("attach"), HashMap.class);
            jsonObj.put("flag", "true");
        } else {
            jsonObj = new HashMap<>();
            jsonObj.put("flag", "false");
        }
        return jsonObj;
    }

    /**
     * 退款
     *
     * @param paramMap
     * @return
     * @throws UserException
     */
    @Override
    public boolean refundOrder(Map<String, String> paramMap) throws UserException {
        Map<String, String> map = new HashMap<>();
        map.put("appid", appid);
        map.put("mch_id", mch_id);
        map.put("nonce_str", S2.randomNumberString(6));

        //退款单参数
        map.put("out_trade_no", paramMap.get("payid"));
        map.put("out_refund_no", paramMap.get("aofid"));
        map.put("refund_account", "REFUND_SOURCE_RECHARGE_FUNDS"); // 从可用余额退款 REFUND_SOURCE_UNSETTLED_FUNDS 未结算资金退款，REFUND_SOURCE_RECHARGE_FUNDS 可用余额退款
        map.put("total_fee", new BigDecimal(paramMap.get("total_fee")).multiply(new BigDecimal(100)).intValue() + "");
        map.put("refund_fee", new BigDecimal(paramMap.get("refund_fee")).multiply(new BigDecimal(100)).intValue() + "");
        map.put("op_user_id", paramMap.get("userid"));
        String reason = paramMap.get("reason");
        if (S.isEmpty(reason)) {
            reason = "美多拼-消费";
        }
        map.put("refund_desc", info_des + "-" + reason);
        String xmlStr = getSign(map, "xml");
        String result = "";
        try {
            result = doPostSSL(this.orderRefundUrl, xmlStr, "", sslpath, mch_id);
        } catch (Exception ex) {
            result = ex.getMessage();
            throw new UserException(ex.getMessage());
        } finally {
            saveParamResult(this.orderRefundUrl, map, result);
        }
        try {
            Map<String, String> resultMap = PayUtils.getMapFromXML(result);
            if (S.eq(resultMap.get("return_code"), GlobalValues.WEIXINRESULT_FAIL.value())) {
                throw new UserException(resultMap.get("return_msg"));
            }
            if (S.eq(resultMap.get("result_code"), GlobalValues.WEIXINRESULT_FAIL.value())) {
                throw new UserException(resultMap.get("err_code_des"));
            }
            if (S.eq(getSign(map, "sign"), resultMap.get("sign"))) {
                throw new UserException("返回结果验证签名失败");
            }
            return true;
        } catch (Exception ex) {
            throw new UserException(ex.getMessage());
        }
    }

    /**
     * 签名
     */
    @Override
    public String getSign(Map<String, String> map, String strType) {
        List list = new ArrayList<>(map.keySet());
        String apiKey = api_key;
        Collections.sort(list);
        StringBuilder queryString = new StringBuilder();
        StringBuilder xmlString = new StringBuilder("<xml>");
        for (Object aList : list) {
            if (S.isNotEmpty(map.get(aList))) {
                if (aList.equals("sign")) {
                    continue;
                }
                queryString.append(aList).append("=").append(map.get(aList));
                queryString.append("&");
                xmlString.append("<").append(aList).append(">").append("<![CDATA[").append(map.get(aList)).append("]]>").append("</" + aList + ">");
            }
        }
        queryString.append("key=").append(apiKey);
        String sign = EncyUtils.MD5(queryString.toString(), "").toUpperCase();
        xmlString.append("<sign>").append(sign).append("</sign>");
        xmlString.append("</xml>");
        String result = "";
        switch (strType) {
            case "sign":
                result = sign;
                break;
            case "xml":
                result = xmlString.toString();
                break;
            case "query":
                result = queryString.toString();
                break;
        }
        return result;
    }

    /**
     * 转账
     *
     * @param paramMap
     * @return
     */
    @Override
    public Map<String, String> transfer(Map<String, String> paramMap) throws Exception {
        Map<String, String> map = new HashMap<>();
        map.put("mch_appid", appid);
        map.put("mchid", mch_id);
        map.put("nonce_str", S2.randomNumberString(6));

        //转账订单号
        map.put("partner_trade_no", paramMap.get("payid"));
        map.put("openid", paramMap.get("account"));//支付账户

        map.put("check_name", "NO_CHECK");//FORCE_CHECK强校验姓名,NO_CHECK不校验真实姓名
//        map.put("check_name","FORCE_CHECK");//FORCE_CHECK强校验姓名,NO_CHECK不校验真实姓名
//        map.put("re_user_name",paramMap.get("realname"));

        map.put("amount", new BigDecimal(paramMap.get("amount")).multiply(new BigDecimal(100)).intValue() + "");
        map.put("desc", paramMap.get("desc"));
        map.put("spbill_create_ip", paramMap.get("ipAddress"));

        Map<String, String> result;
//        try {
        result = doPostUrlSSL(transfersUrl, map);
//        } catch (Exception ex) {
//            AppEntry.LOG.error(ex.getMessage());
//            throw new UserException(ErrorCode.E401002);
//        }
        return result;
    }


    @Override
    public Map<String, Object> selTransfer(Map<String, Object> map) {
        return null;
    }

    @Override
    public String goWallet(Map<String, Object> map) {
        return null;
    }


    @Override
    public String getSignown(Map<String, Object> map, String strType) {
        return null;
    }

    /**
     * 回调数据验证及处理
     *
     * @param map
     * @return
     */
    @Override
    public Map<String, Object> payCallback(Map<String, String> map, ActionContext context) {
        Map<String, Object> param = new HashMap<>();
        String xmlStr = MapUtil.getStr(map, "_body");
        try {
            Map<String, String> params = PayUtils.getMapFromXML(xmlStr);
            String payid = params.get("out_trade_no");
            param.put("payid", payid);
            String return_code = params.get("return_code");
            Map jsonObj = Redis.payDb.getBucket(payid, HashMap.class).get();
            param.put("signVerified", "false");
            param.put("result_code", params.get("result_code"));
            param.put("time_end", params.get("time_end"));
            param.put("amount", new BigDecimal(MapUtil.getStr(params, "total_fee")).divide(new BigDecimal("100"), BigDecimal.ROUND_HALF_UP, 2));
            jsonObj.forEach((k, v) -> param.put(k + "", v));
            if ("SUCCESS".equals(return_code) && getSign(params, "sign").equals(params.get("sign"))) {
                param.put("signVerified", "true");
            } else {//验证失败
                throw new UserException("支付回调验证失败");
            }

        } catch (ParserConfigurationException | IOException | SAXException e) {
            e.printStackTrace();
        }
        return param;
    }

}
